/*
 * The MIT License
 *
 * Copyright 2012 Anonymous.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package pentomino.logic;

/**
 *
 * Represents a two-dimensional array (matrix) of bits
 * as a 32bit integer value with random access for every bit.
 */
class BitField {
    protected static final int MAX_SIZE = 32; // A maximum of elements count.
    
    protected int cols; // Current number of columns.
    protected int rows; // Current number of rows.
    
    protected int data; // The number that keeps all the matrix data.
    
    /*============== Constructors ==============*/
    
    /**
     * Creates an instance of the BitField class by default.
     */
    public BitField()
    {
        this(0, 3, 3);
    }
    
    /**
     * Creates an instance of the BitField class by the specified size.
     * 
     * @param columns number of columns in the matrix
     * @param rows number of rows in the matrix
     */
    public BitField(int columns, int rows)
    {
        this(0, columns, rows);
    }
    
    /**
     * Creates and instance of the BitField class by the specified size 
     * and data (presented as an integer value - sequence of bits).
     * 
     * @param data a default matrix date (sequence of bits)
     * @param columns number of columns in the matrix
     * @param rows number of rows in the matrix
     */
    public BitField(int data, int columns, int rows)
    {
        if (columns < 1 || rows < 1)
            throw new IllegalArgumentException("The 'columns' and 'rows' arguments should be a natural numbers.");
        
        if (columns * rows > MAX_SIZE)
            throw new IllegalArgumentException("You can't create a bit field with more than 32 elements.");
        
        this.data = data;
        this.cols = columns;
        this.rows = rows;
    }
    
    /*============== Member access ==============*/
    
    /**
     * Gets a matrix data.
     * 
     * @return a sequence of bits as an integer value
     */
    public int getData()
    {
        return data;
    }
    
    /**
     * Fills the matrix with the values from specified source.
     * 
     * @param source a sequence of bits as an integer value
     */
    public void setData(int source)
    {
        data = source;
    }
    
    /**
     * Gets a matrix size.
     * 
     * @return number of rows in the matrix
     */
    public int getRows()
    {
        return rows;
    }
    
    /**
     * Sets a matrix size.
     * 
     * @param rows a new value of the matrix rows count
     */
    public void setRows(int rows)
    {
        if (rows < 1)
            throw new IllegalArgumentException("The 'rows' argument should be a natural number.");
        
        if (rows * this.cols > MAX_SIZE)
            throw new IllegalArgumentException("You can't create a bit field with more than 32 elements.");
        
        this.rows = rows;
    }
    
    /**
     * Gets a matrix size.
     * 
     * @return number of columns in the matrix
     */
    public int getColumns()
    {
        return cols;
    }
    
    /**
     * Sets a matrix size.
     * 
     * @param columns a new value of the matrix columns count
     */
    public void setColumns(int columns)
    {
        if (columns < 1)
            throw new IllegalArgumentException("The 'columns' argument should be a natural number.");
        
        if (columns * this.rows > MAX_SIZE)
            throw new IllegalArgumentException("You can't create a bit field with more than 32 elements.");
        
        this.cols = columns;
    }
    
    /*============== Methods ==============*/
    
    /**
     * Applies a bit-mask to the matrix data
     * using simple & (and) bit-operation.
     * 
     * @param mask a bit-mask
     */
    public void and(int mask)
    {
        data &= mask;
    }
    
    /**
     * Applies a bit-mask to the matrix data
     * using simple | (or) bit-operation.
     * 
     * @param mask a bit-mask
     */
    public void or(int mask)
    {
        data |= mask;
    }
    
    /**
     * Applies a bit-mask to the matrix data
     * using simple ^ (xor) bit-operation.
     * 
     * @param mask a bit-mask
     */
    public void xor(int mask)
    {
        data ^= mask;
    }
    
    /**
     * Gets a value of the bit in the specified position in the matrix.
     * 
     * @param row a bit row number
     * @param column a bit column number
     * @return a bit value (0 or 1)
     */
    public byte bitAt(int row, int column)
    {
        if (row < 0 || column < 0)
            throw new IllegalArgumentException("The 'row' and 'column' arguments should be a natural numbers or zero.");
        
        if (row >= rows || column >= cols)
            throw new IndexOutOfBoundsException();
        
        int size = rows * cols - 1;
        int delta = size  - (row * cols + column);
        int mask = 1 << delta;
        int temp = data & mask;
        
        temp >>= delta;
        
        return (byte)temp;
    }
    
    /**
     * Toggles a bit value in the specified position in the matrix.
     * 
     * @param row a bit row number
     * @param column a bit column number
     */
    public void toggleBitAt(int row, int column)
    {
        if (row < 0 || column < 0)
            throw new IllegalArgumentException("The 'row' and 'column' arguments should be a natural numbers or zero.");
        
        if (row >= rows || column >= cols)
            throw new IndexOutOfBoundsException();
        
        int size = rows * cols - 1;
        int delta = size  - (row * cols + column);
        int mask = 1 << delta;
        
        data ^= mask;
    }
    
    /**
     * Sets a bit in specified position in the matrix.
     * 
     * @param row a bit row number
     * @param column a bit column number
     * @param value a new value of the bit (0 or 1)
     */
    public void setBitAt(int row, int column, byte value)
    {
        if (row < 0 || column < 0)
            throw new IllegalArgumentException("The 'row' and 'column' arguments should be a natural numbers or zero.");
        
        if (row >= rows || column >= cols)
            throw new IndexOutOfBoundsException();
        
        if (value < 0 || value > 1)
            throw new IllegalArgumentException("The 'value' argument should be a 1 or zero (bit value).");
        
        byte a = bitAt(row, column);
        
        if (a != value)
            toggleBitAt(row, column);
    }
    
    /**
     * Inverts the matrix (flip) from left to right (horizontal).
     */
    public void flipHorizontal()
    {
        int m = cols / 2;
        
        for (int i = 0; i < rows; i++)
            for (int j = 0; j < m; j++)
                swapBits(i, j, i, cols - (j + 1));
    }
    
    /**
     * Inverts the matrix (flip) from top to bottom (vertical).
     */
    public void flipVertical()
    {
        int m = rows / 2;
        
        for (int j = 0; j < cols; j++)
            for (int i = 0; i < m; i++)
                swapBits(i, j, rows - (i + 1), j);
    }
    
    /**
     * Exchanges a bits values at the specified positions of the matrix.
     * 
     * @param rowA an 'A' bit row number
     * @param colA an 'A' bit column number
     * @param rowB an 'B' bit row number
     * @param colB an 'B' bit column number
     */
    protected void swapBits(int rowA, int colA, int rowB, int colB)
    {
        byte a = bitAt(rowA, colA);
        byte b = bitAt(rowB, colB);
        
        if (a == b)
            return;
        else
        {
            toggleBitAt(rowA, colA);
            toggleBitAt(rowB, colB);
        }
    }
    
    /**
     * Converts the matrix into a string value for print (output).
     */
    @Override
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        
        for (int i = 0; i < rows; i++)
        {
            for (int j = 0; j < cols; j++)
            {
                sb.append(String.format("%d ", bitAt(i, j)));
            }
            
            sb.append("\n");
        }
        
        return sb.toString();
    }
}
